<!-- Demo version: 2018.10.25 -->

<!DOCTYPE html>
<html lang="en">

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta charset="utf-8">

    <link rel="stylesheet" href="https://cdn.webrtc-experiment.com/style.css">

    <title>WebRTC Scalable Screen+Audio Broadcast using RTCMultiConnection</title>

    <meta name="description" content="This module simply initializes socket.io and configures it in a way that single audio/video/screen stream can be shared/relayed over unlimited users without any bandwidth/CPU usage issues. Everything happens peer-to-peer!" />
    <meta name="keywords" content="WebRTC,RTCMultiConnection,Demos,Experiments,Samples,Examples" />

    <style>
        video {
            object-fit: fill;
            width: 100%;
            max-width: 100%;
        }
        button,
        input,
        select {
            font-weight: normal;
            padding: 2px 4px;
            text-decoration: none;
            display: inline-block;
            text-shadow: none;
            font-size: 16px;
            outline: none;
        }

        .make-center {
            text-align: center;
            padding-top: 5px;
        }

        #video-preview {
            margin-bottom: -13px;
        }

        button, input, select {
            font-family: Myriad, Arial, Verdana;
            font-weight: normal;
            border-top-left-radius: 3px;
            border-top-right-radius: 3px;
            border-bottom-right-radius: 3px;
            border-bottom-left-radius: 3px;
            padding: 4px 12px;
            text-decoration: none;
            color: rgb(27, 26, 26);
            display: inline-block;
            box-shadow: rgb(255, 255, 255) 1px 1px 0px 0px inset;
            text-shadow: none;
            background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0.05, rgb(241, 241, 241)), to(rgb(230, 230, 230)));
            font-size: 20px;
            border: 1px solid red;
            outline:none;
            vertical-align: middle;
        }
        button, select {
            height: 35px;
            margin: 0 5px;
        }

        button:hover, input:hover, select:hover {
            background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(5%, rgb(221, 221, 221)), to(rgb(250, 250, 250)));
            border: 1px solid rgb(142, 142, 142);
        }

        button:active, input:active, select:active, button:focus, input:focus, select:focus {
            background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(5%, rgb(183, 183, 183)), to(rgb(255, 255, 255)));
            border: 1px solid rgb(142, 142, 142);
        }
        button[disabled], iput[disabled], select[disabled] {
            background: rgb(249, 249, 249);
            border: 1px solid rgb(218, 207, 207);
            color: rgb(197, 189, 189);
        }
        input, input:focus, input:active {
            background: white;
        }
    </style>
</head>

<body>
    <article>

        <header style="text-align: center;">
            <h1><a href="https://github.com/muaz-khan/WebRTC-Scalable-Broadcast">WebRTC Scalable Screen+Audio Broadcast</a> using <a href="https://github.com/muaz-khan/RTCMultiConnection">RTCMultiConnection</a></h1>
            <p>
                <a href="https://rtcmulticonnection.herokuapp.com/">HOME</a>
                <span> &copy; </span>
                <a href="http://www.MuazKhan.com/" target="_blank">Muaz Khan</a> .
                <a href="http://twitter.com/WebRTCWeb" target="_blank" title="Twitter profile for WebRTC Experiments">@WebRTCWeb</a> .
                <a href="https://github.com/muaz-khan?tab=repositories" target="_blank" title="Github Profile">Github</a> .
                <a href="https://github.com/muaz-khan/RTCMultiConnection/issues?state=open" target="_blank">Latest issues</a> .
                <a href="https://github.com/muaz-khan/RTCMultiConnection/commits/master" target="_blank">What's New?</a>
            </p>
        </header>

        <div class="github-stargazers"></div>

        <section class="experiment make-center">
            <div>
                <input type="text" id="broadcast-id" placeholder="broadcast-id" value="room-xyz">
                <button id="open-or-join">Open or Join Broadcast</button>

                <div id="room-urls" style="text-align: center;display: none;background: #F1EDED;margin: 15px -10px;border: 1px solid rgb(189, 189, 189);border-left: 0;border-right: 0;"></div>
            </div>

            <br>

            <video id="video-preview" controls autoplay></video>
        </section>

        <blockquote>
            This module simply initializes socket.io and configures it in a way that single audio/video/screen stream can be shared/relayed over unlimited users without any <a href="https://www.webrtc-experiment.com/docs/RTP-usage.html">bandwidth/CPU usage issues</a>. Everything happens peer-to-peer!

            <br><br>
            You can try <a href="/demos/Scalable-Broadcast.html">Scalable Video Broadcast</a>!

            <br><br>
            Requirements: <a href="https://chrome.google.com/webstore/detail/screen-capturing/ajhifddimkapgcifgcodmmfdlknahffk">install this Chrome extension</a> or <a href="https://addons.mozilla.org/en-US/firefox/addon/enable-screen-capturing/">this Firefox extension</a>.
        </blockquote>

        <script src="/dist/RTCMultiConnection.min.js"></script>
        <script src="/node_modules/webrtc-adapter/out/adapter.js"></script>
        <script src="/socket.io/socket.io.js"></script>

        <!-- capture screen from any HTTPs domain! -->
        <script src="/node_modules/webrtc-screen-capturing/getScreenId.js"></script>

        <!-- <script src="https://cdn.webrtc-experiment.com/RecordRTC.js"></script> -->
        <script>
            // recording is disabled because it is resulting for browser-crash
            // if you enable below line, please also uncomment above "RecordRTC.js"
            var enableRecordings = false;

            var connection = new RTCMultiConnection(null, {
                useDefaultDevices: true // if we don't need to force selection of specific devices
            });

            // Using getScreenId.js to capture screen from any domain
            // You do NOT need to deploy Chrome Extension YOUR-Self!!
            connection.getScreenConstraints = function(callback) {
                getScreenConstraints(function(error, screen_constraints) {
                    if (!error) {
                        screen_constraints = connection.modifyScreenConstraints(screen_constraints);
                        callback(error, screen_constraints);
                    }
                });
            };

            // its mandatory in v3
            connection.enableScalableBroadcast = true;

            // each relaying-user should serve only 1 users
            connection.maxRelayLimitPerUser = 1;

            // we don't need to keep room-opened
            // scalable-broadcast.js will handle stuff itself.
            connection.autoCloseEntireSession = true;

            // by default, socket.io server is assumed to be deployed on your own URL
            connection.socketURL = '/';

            // comment-out below line if you do not have your own socket.io server
            // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/';

            connection.socketMessageEvent = 'scalable-screen-plus-audio-broadcast-demo';

            // document.getElementById('broadcast-id').value = connection.userid;

            // user need to connect server, so that others can reach him.
            connection.connectSocket(function(socket) {
                socket.on('logs', function(log) {
                    document.querySelector('h1').innerHTML = log.replace(/</g, '----').replace(/>/g, '___').replace(/----/g, '(<span style="color:red;">').replace(/___/g, '</span>)');
                });

                // this event is emitted when a broadcast is already created.
                socket.on('join-broadcaster', function(hintsToJoinBroadcast) {
                    console.log('join-broadcaster', hintsToJoinBroadcast);

                    connection.session = hintsToJoinBroadcast.typeOfStreams;
                    connection.sdpConstraints.mandatory = {
                        OfferToReceiveVideo: true,
                        OfferToReceiveAudio: true
                    };
                    connection.join(hintsToJoinBroadcast.userid);
                });

                socket.on('rejoin-broadcast', function(broadcastId) {
                    console.log('rejoin-broadcast', broadcastId);

                    connection.attachStreams = [];
                    socket.emit('check-broadcast-presence', broadcastId, function(isBroadcastExists) {
                        if(!isBroadcastExists) {
                            // the first person (i.e. real-broadcaster) MUST set his user-id
                            connection.userid = broadcastId;
                        }

                        socket.emit('join-broadcast', {
                            broadcastId: broadcastId,
                            userid: connection.userid,
                            typeOfStreams: connection.session
                        });
                    });
                });

                socket.on('broadcast-stopped', function(broadcastId) {
                    // alert('Broadcast has been stopped.');
                    // location.reload();
                    console.error('broadcast-stopped', broadcastId);
                    alert('This broadcast has been stopped.');
                });

                // this event is emitted when a broadcast is absent.
                socket.on('start-broadcasting', function(typeOfStreams) {
                    console.log('start-broadcasting', typeOfStreams);

                    // host i.e. sender should always use this!
                    connection.sdpConstraints.mandatory = {
                        OfferToReceiveVideo: false,
                        OfferToReceiveAudio: false
                    };
                    connection.session = typeOfStreams;

                    // "open" method here will capture media-stream
                    // we can skip this function always; it is totally optional here.
                    // we can use "connection.getUserMediaHandler" instead
                    connection.captureUserMedia(function(audioStream) {
                        connection.captureUserMedia(function(screenStream) {
                            screenStream.addTrack(audioStream.getAudioTracks()[0]);
                            connection.dontCaptureUserMedia = true;
                            connection.open(connection.userid, function() {
                                showRoomURL(connection.sessionid);
                            });
                        }, {screen: true});
                    }, {audio: true});
                });
            });

            window.onbeforeunload = function() {
                // Firefox is ugly.
                document.getElementById('open-or-join').disabled = false;
            };

            var videoPreview = document.getElementById('video-preview');

            connection.onstream = function(event) {
                if(event.stream.isAudio) {
                    event.mediaElement.pause();
                    delete event.mediaElement;
                    return;
                }

                if(connection.isInitiator && event.type !== 'local') {
                    return;
                }

                connection.isUpperUserLeft = false;

                videoPreview.src = URL.createObjectURL(event.stream);
                videoPreview.play();

                videoPreview.userid = event.userid;

                if(event.type === 'local') {
                    videoPreview.muted = true;
                }

                if(event.mediaElement) {
                    event.mediaElement.pause();
                    delete event.mediaElement;
                }

                if (connection.isInitiator == false && event.type === 'remote') {
                    // he is merely relaying the media
                    connection.dontCaptureUserMedia = true;
                    connection.attachStreams.push(event.stream);
                    connection.sdpConstraints.mandatory = {
                        OfferToReceiveAudio: false,
                        OfferToReceiveVideo: false
                    };

                    connection.getSocket(function(socket) {
                        socket.emit('can-relay-broadcast');

                        if(connection.DetectRTC.browser.name === 'Chrome') {
                            connection.getAllParticipants().forEach(function(p) {
                                if(p + '' != event.userid + '') {
                                    var peer = connection.peers[p].peer;
                                    peer.getLocalStreams().forEach(function(localStream) {
                                        peer.removeStream(localStream);
                                    });
                                    peer.addStream(event.stream);
                                    connection.dontAttachStream = true;
                                    connection.renegotiate(p);
                                    connection.dontAttachStream = false;
                                }
                            });
                        }

                        if(connection.DetectRTC.browser.name === 'Firefox') {
                            // Firefox is NOT supporting removeStream method
                            // that's why using alternative hack.
                            // NOTE: Firefox seems unable to replace-tracks of the remote-media-stream
                            // need to ask all deeper nodes to rejoin
                            connection.getAllParticipants().forEach(function(p) {
                                if(p + '' != event.userid + '') {
                                    connection.replaceTrack(event.stream, p);
                                }
                            });
                        }

                        // Firefox seems UN_ABLE to record remote MediaStream
                        // WebAudio solution merely records audio
                        // so recording is skipped for Firefox.
                        if(connection.DetectRTC.browser.name === 'Chrome') {
                            repeatedlyRecordStream(event.stream);
                        }
                    });
                }
            };

            // ask node.js server to look for a broadcast
            // if broadcast is available, simply join it. i.e. "join-broadcaster" event should be emitted.
            // if broadcast is absent, simply create it. i.e. "start-broadcasting" event should be fired.
            document.getElementById('open-or-join').onclick = function() {
                var broadcastId = document.getElementById('broadcast-id').value;
                if (broadcastId.replace(/^\s+|\s+$/g, '').length <= 0) {
                    alert('Please enter broadcast-id');
                    document.getElementById('broadcast-id').focus();
                    return;
                }

                document.getElementById('open-or-join').disabled = true;

                connection.extra.broadcastId = broadcastId;

                connection.session = {
                    screen: true,
                    oneway: true
                };

                connection.getSocket(function(socket) {
                    socket.emit('check-broadcast-presence', broadcastId, function(isBroadcastExists) {
                        if(!isBroadcastExists) {
                            // the first person (i.e. real-broadcaster) MUST set his user-id
                            connection.userid = broadcastId;
                        }

                        console.log('check-broadcast-presence', broadcastId, isBroadcastExists);

                        socket.emit('join-broadcast', {
                            broadcastId: broadcastId,
                            userid: connection.userid,
                            typeOfStreams: connection.session
                        });
                    });
                });
            };

            connection.onstreamended = function() {};

            connection.onleave = function(event) {
                if(event.userid !== videoPreview.userid) return;

                connection.getSocket(function(socket) {
                    socket.emit('can-not-relay-broadcast');

                    connection.isUpperUserLeft = true;

                    if(allRecordedBlobs.length) {
                        // playing lats recorded blob
                        var lastBlob = allRecordedBlobs[allRecordedBlobs.length - 1];
                        videoPreview.src = URL.createObjectURL(lastBlob);
                        videoPreview.play();
                        allRecordedBlobs = [];
                    }
                    else if(connection.currentRecorder) {
                        var recorder = connection.currentRecorder;
                        connection.currentRecorder = null;
                        recorder.stopRecording(function() {
                            if(!connection.isUpperUserLeft) return;

                            videoPreview.src = URL.createObjectURL(recorder.blob);
                            videoPreview.play();
                        });
                    }

                    if(connection.currentRecorder) {
                        connection.currentRecorder.stopRecording();
                        connection.currentRecorder = null;
                    }
                });
            };

            var allRecordedBlobs = [];

            function repeatedlyRecordStream(stream) {
                if(!enableRecordings) {
                    return;
                }

                connection.currentRecorder = RecordRTC(stream, {
                    type: 'video'
                });

                connection.currentRecorder.startRecording();

                setTimeout(function() {
                    if(connection.isUpperUserLeft || !connection.currentRecorder) {
                        return;
                    }

                    connection.currentRecorder.stopRecording(function() {
                        allRecordedBlobs.push(connection.currentRecorder.blob);

                        if(connection.isUpperUserLeft) {
                            return;
                        }

                        connection.currentRecorder = null;
                        repeatedlyRecordStream(stream);
                    });
                }, 30 * 1000); // 30-seconds
            };

            function disableInputButtons() {
                document.getElementById('open-or-join').disabled = true;
                document.getElementById('broadcast-id').disabled = true;
            }

            // ......................................................
            // ......................Handling broadcast-id................
            // ......................................................

            function showRoomURL(broadcastId) {
                var roomHashURL = '#' + broadcastId;
                var roomQueryStringURL = '?broadcastId=' + broadcastId;

                var html = '<h2>Unique URL for your room:</h2><br>';

                html += 'Hash URL: <a href="' + roomHashURL + '" target="_blank">' + roomHashURL + '</a>';
                html += '<br>';
                html += 'QueryString URL: <a href="' + roomQueryStringURL + '" target="_blank">' + roomQueryStringURL + '</a>';

                var roomURLsDiv = document.getElementById('room-urls');
                roomURLsDiv.innerHTML = html;

                roomURLsDiv.style.display = 'block';
            }

            (function() {
                var params = {},
                    r = /([^&=]+)=?([^&]*)/g;

                function d(s) {
                    return decodeURIComponent(s.replace(/\+/g, ' '));
                }
                var match, search = window.location.search;
                while (match = r.exec(search.substring(1)))
                    params[d(match[1])] = d(match[2]);
                window.params = params;
            })();

            var broadcastId = '';
            if (localStorage.getItem(connection.socketMessageEvent)) {
                broadcastId = localStorage.getItem(connection.socketMessageEvent);
            } else {
                broadcastId = connection.token();
            }
            document.getElementById('broadcast-id').value = broadcastId;
            document.getElementById('broadcast-id').onkeyup = function() {
                localStorage.setItem(connection.socketMessageEvent, this.value);
            };

            var hashString = location.hash.replace('#', '');
            if(hashString.length && hashString.indexOf('comment-') == 0) {
              hashString = '';
            }

            var broadcastId = params.broadcastId;
            if(!broadcastId && hashString.length) {
                broadcastId = hashString;
            }

            if(broadcastId && broadcastId.length) {
                document.getElementById('broadcast-id').value = broadcastId;
                localStorage.setItem(connection.socketMessageEvent, broadcastId);

                // auto-join-room
                (function reCheckRoomPresence() {
                    connection.checkPresence(broadcastId, function(isRoomExists) {
                        if(isRoomExists) {
                            document.getElementById('open-or-join').onclick();
                            return;
                        }

                        setTimeout(reCheckRoomPresence, 5000);
                    });
                })();

                disableInputButtons();
            }

        </script>

        <section class="experiment own-widgets latest-commits">
            <h2 class="header" id="updates" style="color: red;padding-bottom: .1em;"><a href="https://github.com/muaz-khan/RTCMultiConnection/commits/master">Latest Updates</a></h2>
            <div id="github-commits"></div>
        </section>

        <section class="experiment own-widgets">
            <h2 class="header" id="updates" style="color: red;padding-bottom: .1em;"><a href="https://github.com/muaz-khan/RTCMultiConnection/issues">Latest Issues</a></h2>
            <div id="github-issues"></div>
        </section>

        <section class="experiment">
            <h2 class="header" id="feedback">Feedback</h2>
            <div>
                <textarea id="message" style="height: 8em; margin: .2em; width: 98%; border: 1px solid rgb(189, 189, 189); outline: none; resize: vertical;" placeholder="Have any message? Suggestions or something went wrong?"></textarea>
            </div>
            <button id="send-message" style="font-size: 1em;">Send Message</button><small style="margin-left:1em;">Enter your email too; if you want "direct" reply!</small>
        </section>

        <a href="https://github.com/muaz-khan/RTCMultiConnection" class="fork-left"></a>

        <script>
            window.useThisGithubPath = 'muaz-khan/RTCMultiConnection';
        </script>

        <script src="https://cdn.webrtc-experiment.com/commits.js" async></script>

    </article>

    <footer>
        <p>
            <a href="https://www.webrtc-experiment.com">WebRTC Experiments</a> © <a href="https://plus.google.com/+MuazKhan" rel="author" target="_blank">Muaz Khan</a>
            <a href="mailto:muazkh@gmail.com" target="_blank">muazkh@gmail.com</a>
            <a href="https://github.com/muaz-khan" target="_blank">Github</a>
        </p>
    </footer>

</body>

</html>
